//
// Created by yasin on 2024/9/5.
//

#include "signaling_client.h"
#include "rtc_log.h"
#include "http_utils.h"
#include "json_helper.h"
#include "nlohmann/json.hpp"

#include "rtc_base/thread.h"

const char* MOCK_SDP = "v=0\n"
                  "o=- 3844756355845763328 2 IN IP4 127.0.0.1\n"
                  "s=-\n"
                  "t=0 0\n"
                  "a=group:BUNDLE 0 1 2\n"
                  "a=extmap-allow-mixed\n"
                  "a=msid-semantic: WMS ARDAMS\n"
                  "m=video 9 UDP/TLS/RTP/SAVPF 96 97 39 40 98 99 127 103 104 105 106 107 108\n"
                  "c=IN IP4 0.0.0.0\n"
                  "a=rtcp:9 IN IP4 0.0.0.0\n"
                  "a=ice-ufrag:gkoL\n"
                  "a=ice-pwd:ezUemZB6p5ncoJUDYx/WXHgX\n"
                  "a=ice-options:trickle renomination\n"
                  "a=fingerprint:sha-256 11:53:3D:5B:90:B3:28:4F:06:5F:0E:BB:C7:C3:35:A6:11:02:42:38:D6:F5:8B:2C:05:17:62:9B:7F:AF:49:7C\n"
                  "a=setup:actpass\n"
                  "a=mid:0\n"
                  "a=extmap:1 urn:ietf:params:rtp-hdrext:toffset\n"
                  "a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\n"
                  "a=extmap:3 urn:3gpp:video-orientation\n"
                  "a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\n"
                  "a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\n"
                  "a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\n"
                  "a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\n"
                  "a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space\n"
                  "a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid\n"
                  "a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\n"
                  "a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\n"
                  "a=sendrecv\n"
                  "a=msid:ARDAMS ARDAMSv0\n"
                  "a=rtcp-mux\n"
                  "a=rtcp-rsize\n"
                  "a=rtpmap:96 VP8/90000\n"
                  "a=rtcp-fb:96 goog-remb\n"
                  "a=rtcp-fb:96 transport-cc\n"
                  "a=rtcp-fb:96 ccm fir\n"
                  "a=rtcp-fb:96 nack\n"
                  "a=rtcp-fb:96 nack pli\n"
                  "a=rtpmap:97 rtx/90000\n"
                  "a=fmtp:97 apt=96\n"
                  "a=rtpmap:39 AV1/90000\n"
                  "a=rtcp-fb:39 goog-remb\n"
                  "a=rtcp-fb:39 transport-cc\n"
                  "a=rtcp-fb:39 ccm fir\n"
                  "a=rtcp-fb:39 nack\n"
                  "a=rtcp-fb:39 nack pli\n"
                  "a=fmtp:39 level-idx=5;profile=0;tier=0\n"
                  "a=rtpmap:40 rtx/90000\n"
                  "a=fmtp:40 apt=39\n"
                  "a=rtpmap:98 VP9/90000\n"
                  "a=rtcp-fb:98 goog-remb\n"
                  "a=rtcp-fb:98 transport-cc\n"
                  "a=rtcp-fb:98 ccm fir\n"
                  "a=rtcp-fb:98 nack\n"
                  "a=rtcp-fb:98 nack pli\n"
                  "a=fmtp:98 profile-id=0\n"
                  "a=rtpmap:99 rtx/90000\n"
                  "a=fmtp:99 apt=98\n"
                  "a=rtpmap:127 H264/90000\n"
                  "a=rtcp-fb:127 goog-remb\n"
                  "a=rtcp-fb:127 transport-cc\n"
                  "a=rtcp-fb:127 ccm fir\n"
                  "a=rtcp-fb:127 nack\n"
                  "a=rtcp-fb:127 nack pli\n"
                  "a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\n"
                  "a=rtpmap:103 rtx/90000\n"
                  "a=fmtp:103 apt=127\n"
                  "a=rtpmap:104 H265/90000\n"
                  "a=rtcp-fb:104 goog-remb\n"
                  "a=rtcp-fb:104 transport-cc\n"
                  "a=rtcp-fb:104 ccm fir\n"
                  "a=rtcp-fb:104 nack\n"
                  "a=rtcp-fb:104 nack pli\n"
                  "a=rtpmap:105 rtx/90000\n"
                  "a=fmtp:105 apt=104\n"
                  "a=rtpmap:106 red/90000\n"
                  "a=rtpmap:107 rtx/90000\n"
                  "a=fmtp:107 apt=106\n"
                  "a=rtpmap:108 ulpfec/90000\n"
                  "a=ssrc-group:FID 938873922 3634934354\n"
                  "a=ssrc:938873922 cname:cYwn4yOWwLl722Rm\n"
                  "a=ssrc:938873922 msid:ARDAMS ARDAMSv0\n"
                  "a=ssrc:3634934354 cname:cYwn4yOWwLl722Rm\n"
                  "a=ssrc:3634934354 msid:ARDAMS ARDAMSv0\n"
                  "m=audio 9 UDP/TLS/RTP/SAVPF 111 63 9 102 0 8 13 110 126\n"
                  "c=IN IP4 0.0.0.0\n"
                  "a=rtcp:9 IN IP4 0.0.0.0\n"
                  "a=ice-ufrag:gkoL\n"
                  "a=ice-pwd:ezUemZB6p5ncoJUDYx/WXHgX\n"
                  "a=ice-options:trickle renomination\n"
                  "a=fingerprint:sha-256 11:53:3D:5B:90:B3:28:4F:06:5F:0E:BB:C7:C3:35:A6:11:02:42:38:D6:F5:8B:2C:05:17:62:9B:7F:AF:49:7C\n"
                  "a=setup:actpass\n"
                  "a=mid:1\n"
                  "a=extmap:14 urn:ietf:params:rtp-hdrext:ssrc-audio-level\n"
                  "a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\n"
                  "a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\n"
                  "a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid\n"
                  "a=sendrecv\n"
                  "a=msid:ARDAMS ARDAMSa0\n"
                  "a=rtcp-mux\n"
                  "a=rtpmap:111 opus/48000/2\n"
                  "a=rtcp-fb:111 transport-cc\n"
                  "a=fmtp:111 minptime=10;useinbandfec=1\n"
                  "a=rtpmap:63 red/48000/2\n"
                  "a=fmtp:63 111/111\n"
                  "a=rtpmap:9 G722/8000\n"
                  "a=rtpmap:102 ILBC/8000\n"
                  "a=rtpmap:0 PCMU/8000\n"
                  "a=rtpmap:8 PCMA/8000\n"
                  "a=rtpmap:13 CN/8000\n"
                  "a=rtpmap:110 telephone-event/48000\n"
                  "a=rtpmap:126 telephone-event/8000\n"
                  "a=ssrc:4253232388 cname:cYwn4yOWwLl722Rm\n"
                  "a=ssrc:4253232388 msid:ARDAMS ARDAMSa0\n"
                  "m=application 9 UDP/DTLS/SCTP webrtc-datachannel\n"
                  "c=IN IP4 0.0.0.0\n"
                  "a=ice-ufrag:gkoL\n"
                  "a=ice-pwd:ezUemZB6p5ncoJUDYx/WXHgX\n"
                  "a=ice-options:trickle renomination\n"
                  "a=fingerprint:sha-256 11:53:3D:5B:90:B3:28:4F:06:5F:0E:BB:C7:C3:35:A6:11:02:42:38:D6:F5:8B:2C:05:17:62:9B:7F:AF:49:7C\n"
                  "a=setup:actpass\n"
                  "a=mid:2\n"
                  "a=sctp-port:5000\n"
                  "a=max-message-size:262144\n";

namespace ohosrtc {

SignalingClient::SignalingClient() {
  thread_ = rtc::Thread::CreateWithSocketServer();
  thread_->SetName("ohos-signaling", nullptr);
  thread_->Start();
  ws_client_ = std::make_shared<WebSocketClient>(thread_);
  ws_client_->SetMessageHandler([this](const std::string &message) {
    OnWebSocketMessage(message);
  });
}

void SignalingClient::Connect(const std::string &url,
                              const std::string &room_id,
                              SuccessCallback<const RoomAccessInfo&> success_cb,
                              FailCallback fail_cb) {
  RTC_LOG << "Start connecting, url:" << url << ", room_id:" << room_id;
  room_url_ = url;
  room_id_ = room_id;
  RTC_LOG << "Connecting, http join start";
  HttpPostMessage("/join/" + room_id, [this, success_cb, fail_cb](const nlohmann::json &params) {
    room_access_info_.client_id = params["client_id"].get<std::string>();
    room_access_info_.wss_url = params["wss_url"].get<std::string>();
    room_access_info_.wss_post_url = params["wss_post_url"].get<std::string>();
    room_access_info_.is_initiator = params["is_initiator"].get<std::string>() == "true";
    RTC_LOG << "Connecting, http join success, "
      << ", client_id:" << room_access_info_.client_id
      << ", wss_url:" << room_access_info_.wss_url
      << "wss_post_url" << room_access_info_.wss_post_url
      << ", is_initiator:" << room_access_info_.is_initiator;

    if (!room_access_info_.is_initiator && params.contains("messages")) {
      auto message_array = params["messages"];
      auto it = message_array.begin();
      while (it != message_array.end()) {
        std::string message = it->get<std::string>();
        room_access_info_.room_messages.emplace_back(message);
        it++;
      }
    }

    nlohmann::json pc_config_json = nlohmann::json::parse(params["pc_config"].get<std::string>());
    bool is_turn_present = false;
    if (pc_config_json.contains("iceServers")) {
      auto ice_server_array = pc_config_json["iceServers"];
      auto it = ice_server_array.begin();
      while (it != ice_server_array.end()) {
        auto &ice_server_json = *it;
        IceServer ice_server;
        ice_server.url = ice_server_json["urls"].get<std::string>();
        if (ice_server.url.find("turn:") >= 0) {
          is_turn_present = true;
        }
        if (ice_server_json.contains("credential")) {
          ice_server.passwd = ice_server_json["credential"].get<std::string>();
        }
        room_access_info_.ice_servers.push_back(std::move(ice_server));
        it++;
      }
    }

    if (!is_turn_present && params.contains("ice_server_url")) {
      auto ice_server_url = params["ice_server_url"].get<std::string>();
      HttpGetTurnServers(ice_server_url, [this, success_cb, fail_cb](const std::vector<IceServer> &ice_server_list) {
        connection_state_ = ConnectionState::HttpJoined;
        room_access_info_.ice_servers.insert(room_access_info_.ice_servers.end(), ice_server_list.begin(), ice_server_list.end());
        ConnectSignaling(room_access_info_.wss_url, [this, success_cb]() {
          if (success_cb) {
            success_cb(room_access_info_);
          }
        }, fail_cb);
      }, fail_cb);
    } else {
      connection_state_ = ConnectionState::HttpJoined;
      ConnectSignaling(room_access_info_.wss_url, [this, success_cb]() {
        if (success_cb) {
          success_cb(room_access_info_);
        }
      }, fail_cb);
    }


  }, [fail_cb](std::string error) {
    RTC_LOG << "Connect failed, http join failed " << error;
    fail_cb(error);
  });
}

void SignalingClient::HttpGetTurnServers(const std::string &url,
                                         SuccessCallback<const std::vector<IceServer>> success_cb,
                                         ohosrtc::FailCallback fail_cb) {
  RTC_LOG << "Request TURN from: " << url;
  http_utils::HttpGet(thread_, url, [success_cb](const std::string &response) {
    RTC_LOG << "TURN Response: " << response;
    std::vector<IceServer> ice_server_list;
    nlohmann::json root = nlohmann::json::parse(response);
    auto ice_servers_json = root["iceServers"];
    auto ice_it = ice_servers_json.begin();
    while (ice_it != ice_servers_json.end()) {
      auto &server_json = *ice_it;
      auto username = server_json.contains("username") ? server_json["username"].get<std::string>() : "";
      auto credential = server_json.contains("credential") ? server_json["credential"].get<std::string>() : "";
      if (server_json.contains("urls")) {
        auto urls = server_json["urls"];
        auto url_it = urls.begin();
        while (url_it != urls.end()) {
          IceServer ice_server;
          ice_server.url = url_it->get<std::string>();
          ice_server.username = username;
          ice_server.passwd = credential;
          ice_server_list.emplace_back(std::move(ice_server));
          url_it++;
        }
      }
      ice_it++;
    }
    if (success_cb) {
      success_cb(std::move(ice_server_list));
    }
  }, [fail_cb](const std::string &error) {
    RTC_LOG << "TURN Response error: " << error;
  });
}

void SignalingClient::Disconnect() {
  if (room_url_.empty() || room_id_.empty()) {
    return;
  }
  RTC_LOG << "SignalingClient Disconnect " << "/leave/" + room_id_ + "/" + room_access_info_.client_id;
  http_utils::HttpPost(thread_, room_url_, "/leave/" + room_id_ + "/" + room_access_info_.client_id);
  
  if (connection_state_ == ConnectionState::SignalingConnected) {
    nlohmann::json message = nlohmann::json::object();
    message["type"] = "bye";
    SendSignaling(message.dump());
    http_utils::HttpDelete(thread_, room_access_info_.wss_post_url, "/" + room_id_ + "/" + room_access_info_.client_id);
  }
  ws_client_->Disconnect();
  connection_state_ = ConnectionState::Disconnected;
}

void SignalingClient::HttpPostMessage(const std::string &path,
                               SuccessCallback<const nlohmann::json&> success_cb,
                               ohosrtc::FailCallback fail_cb) {
  http_utils::HttpPost(thread_, room_url_, path, [success_cb, fail_cb](const std::string &response) {
    RTC_LOG << "HTTP Response: " << response;
    nlohmann::json root = nlohmann::json::parse(response);
    std::string result = root["result"].get<std::string>();
    if (result != "SUCCESS") {
      if (fail_cb) {
        fail_cb(result);
      }
    } else {
      if (success_cb) {
        success_cb(root["params"]);
      }
    }
  }, fail_cb);
}


void SignalingClient::ConnectSignaling(const std::string &ws_url, SuccessCallback<> success_cb, ohosrtc::FailCallback fail_cb) {
  RTC_LOG << "Start connect signaling";
  ws_client_->Connect(ws_url, [this, success_cb, fail_cb]() {
    connection_state_ = ConnectionState::WebSocketConnected;
    RTC_LOG << "websocket connected";

    nlohmann::json message = nlohmann::json::object();
    message["cmd"] = "register";
    message["roomid"] = room_id_;
    message["clientid"] = room_access_info_.client_id;
    ws_client_->SendMessage(message.dump(), [this, success_cb]() {
      RTC_LOG << "Connect signaling success";
      OnSignalingConnected();
      if (success_cb) {
        success_cb();
      }
    }, [fail_cb](const std::string &error) {
      RTC_LOG << "Connect signaling failed: " << error;
      if (fail_cb) {
        fail_cb(error);
      }
    });
  }, [](const std::string &error) {
    RTC_LOG << "websocket connect failed";
  });


}

void SignalingClient::OnSignalingConnected() {
  connection_state_ = ConnectionState::SignalingConnected;

  for (auto &signaling : pending_signaling_list_) {
    SendSignaling(signaling);
  }
  pending_signaling_list_.clear();

  // TODO just mock
//  Signaling offer_signaling;
//  offer_signaling.type = "offer";
//  offer_signaling.contents = nlohmann::json::object();
//  offer_signaling.contents["sdp"] = MOCK_SDP;
//  SendSignaling(offer_signaling);
//  Disconnect();
}

void SignalingClient::ConsumeRoomMessages() {
  for (auto &message : room_access_info_.room_messages) {
    OnSignalingMessage(message);
  }
}

void SignalingClient::SendSignaling(const std::string &signaling) {
  if (connection_state_ != ConnectionState::SignalingConnected) {
    RTC_LOG << "send signaling before signaling connected, added to pending list ";
    pending_signaling_list_.emplace_back(signaling);
    return;
  }

  RTC_LOG << "Send signaling: " << signaling;
  nlohmann::json message = nlohmann::json::object();
  message["cmd"] = "send";
  message["msg"] = signaling;
  ws_client_->SendMessage(message.dump());
}

void SignalingClient::SendOfferSdp(const SDP &sdp) {
  RTC_LOG << "send offer";
  nlohmann::json signaling = nlohmann::json::object();
  std::string sdp_str;
  sdp.ToString(&sdp_str);
  signaling["sdp"] = sdp_str;
  signaling["type"] = "offer";
  SendSignaling(signaling.dump());
}

void SignalingClient::SendAnswerSdp(const SDP &sdp) {
  RTC_LOG << "send answer";
  nlohmann::json signaling = nlohmann::json::object();
  std::string sdp_str;
  sdp.ToString(&sdp_str);
  signaling["sdp"] = sdp_str;
  signaling["type"] = "answer";
  SendSignaling(signaling.dump());
}

void SignalingClient::SendLocalIceCandidate(const IceCandidate &ice_candidate) {
  nlohmann::json signaling = nlohmann::json::object();
  signaling["type"] = "candidate";
  signaling["label"] = ice_candidate.sdp_mline_index();
  signaling["id"] = ice_candidate.sdp_mid();
  std::string candidate_str;
  ice_candidate.ToString(&candidate_str);
  signaling["candidate"] = candidate_str;
  SendSignaling(signaling.dump());
}

const RoomAccessInfo &SignalingClient::GetRoomAccessInfo() {
  return room_access_info_;
}

void SignalingClient::SetRemoteSdpHandler(ohosrtc::RemoteSdpHandler handler) {
  remote_sdp_handler_ = handler;
}

void SignalingClient::SetRemoteIceCandidateHandler(ohosrtc::RemoteIceCandidateHandler handler) {
  remote_ice_candidate_handler_ = handler;
}

void SignalingClient::SetRemoteByeHandler(ohosrtc::RemoteByeHandler handler) {
  remote_bye_handler_ = handler;
}

void SignalingClient::OnWebSocketMessage(const std::string &message) {
  nlohmann::json message_json = nlohmann::json::parse(message);

  if (message_json.contains("error")) {
    std::string error = message_json["error"].get<std::string>();
    if (error.size() > 0) {
      RTC_LOG << "Recv websocket error:" << error;
      return;
    }
  }

  OnSignalingMessage(message_json["msg"].get<std::string>());
}

void SignalingClient::OnSignalingMessage(const std::string &message) {
  RTC_LOG << "Recv signaling:" << message;
  nlohmann::json signaling = nlohmann::json::parse(message);
  auto type = signaling["type"].get<std::string>();
  if (type == "offer" || type == "answer") {
    webrtc::SdpParseError parse_error;
    auto sdp = webrtc::CreateSessionDescription(type, signaling["sdp"].get<std::string>(), &parse_error);
    if (!sdp) {
      RTC_LOG << "Parse remote sdp error:" << parse_error.description;
    } else {
      if (remote_sdp_handler_) {
        remote_sdp_handler_(sdp);
      }
    }
  } else if (type == "candidate") {
    int label = signaling["label"].get<int>();
    std::string id = signaling["id"].get<std::string>();
    std::string candidate_str = signaling["candidate"].get<std::string>();
    webrtc::SdpParseError parse_error;
    auto candidate = webrtc::CreateIceCandidate(id, label, candidate_str, &parse_error);
    if (!candidate) {
      RTC_LOG << "Parse ice candidate error:" << parse_error.description;
    } else {
      if (remote_ice_candidate_handler_) {
        remote_ice_candidate_handler_(candidate);
      }
    }

  } else if (type == "bye") {
      if (remote_bye_handler_) {
        remote_bye_handler_();
      }
    }
  }
}